Skip to content

ci: add a Linux aarch64 build - #331

Merged
doublegate merged 1 commit into
doublegate:mainfrom
WizzardSK:ci-linux-aarch64
Jul 30, 2026
Merged

ci: add a Linux aarch64 build#331
doublegate merged 1 commit into
doublegate:mainfrom
WizzardSK:ci-linux-aarch64

Conversation

@WizzardSK

@WizzardSK WizzardSK commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Adds a libretro-build-linux-aarch64 job so the core lands in nightly/linux/aarch64/ on the buildbot, where it is currently missing.

Why it is not a one-liner

There is no rust-linux-aarch64.yml template in ci-templates — only rust-linux-x64.yml — and libretro-build-rust is FROM libretro-build-amd64-ubuntu, so there is no native aarch64 image to point at either. I raised that gap as libretro/libretro-super#2030.

So this job cross-compiles from the same image the x64 job already uses, in the style that image already uses for its other arm64 targets (CARGO_TARGET_AARCH64_APPLE_DARWIN_LINKER and friends): add the target, point CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER at aarch64-linux-gnu-gcc, and override STRIP, since the template's default strip is host binutils (rust-webos.yml does the same for its ARM targets). .libretro-rust-linux-x64 is extended rather than -default, since that is what pins TARGET_ARCH to x86_64.

I read the #91899 post-mortem before writing this. The job declares RUST_TARGET like every other one and runs rustup target add ${RUST_TARGET}, so the rust-toolchain.toml pin gets its rust-std for aarch64 the same way; it spells the before_script out rather than extending .rust-target-install only because the cross linker has to be apt-installed in the same block. Nothing is added to targets in rust-toolchain.toml, per the note there.

If a rust-linux-aarch64.yml template ever lands, this job collapses to an extends: line.

Tested on real aarch64 hardware

Built and run natively on an aarch64 Linux machine (Snapdragon 7c / SC7180, postmarketOS), target aarch64-unknown-linux-gnu. Worth noting for the pin: rustup honoured rust-toolchain.toml and pulled a fresh 1.96.0 aarch64 toolchain on its own, and the build was clean on it.

  • no source changes neededcargo build --release --target aarch64-unknown-linux-gnu is clean across all the workspace crates (cpu, ppu, apu, mappers, core, libretro)
  • librustynes.so, ELF 64-bit LSB shared object, ARM aarch64, 1.9 MB
  • in RetroArch: a homebrew NES ROM boots and plays with correct colours, 60.13 fps against the core's 60.0988 target — full speed
  • 600 frames, exit code 0, no crash

One aside, unrelated to this PR: the core cannot be driven by a minimal libretro host that calls retro_set_environment before retro_init. rust-libretro 0.3.2 panics there with retro_set_environment: Core has not been initialized yet! (rust-libretro-0.3.2/src/lib.rs:432). RetroArch happens to call them in the other order so it never trips, but a stricter frontend would. That is upstream rust-libretro, not this repo — flagging it only because it cost me a confusing crash while benchmarking.

What I could not test: I built natively on aarch64, whereas this job cross-compiles from x86_64. I also cannot run GitLab pipelines from a GitHub PR, so the job itself has not been executed by CI yet.

Summary by CodeRabbit

  • New Features
    • Added automated Linux ARM64 builds for the libretro core.
    • The new build configuration cross-compiles the core for aarch64-unknown-linux-gnu, enabling support for ARM64 environments.

There is no rust-linux-aarch64.yml template yet
(libretro/libretro-super#2030) and the rust build image is amd64-only, so
this cross-compiles from that image the same way it already cross-compiles
the Apple and Android arm64 targets: add the target, the cross linker via
CARGO_TARGET_*_LINKER, and an aarch64 strip.

before_script inlines the rustup target add rather than extending
.rust-target-install, since the cross linker has to be apt-installed in the
same block; the toolchain-pin handling from the #91899 post-mortem is
unchanged.
@WizzardSK
WizzardSK requested a review from doublegate as a code owner July 28, 2026 16:30
@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Currently processing new changes in this PR. This may take a few minutes, please wait...

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: faf98673-1007-4db8-9e8d-9dea9301b55c

📥 Commits

Reviewing files that changed from the base of the PR and between a1df7b9 and 20eb50b.

📒 Files selected for processing (1)
  • .gitlab-ci.yml
 ___________________________________________________________________________________________________________________________________
< Don't use manual procedures. A shell script or batch file will execute the same instructions, in the same order, time after time. >
 -----------------------------------------------------------------------------------------------------------------------------------
  \
   \   (\__/)
       (•ㅅ•)
       /   づ
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

@doublegate

doublegate commented Jul 30, 2026

Copy link
Copy Markdown
Owner

Thanks for a genuinely excellent PR, @WizzardSK — the write-up, the upstream-gap issue (libretro/libretro-super#2030), and the fact that you read the #91899 post-mortem before touching the toolchain all show. Adopted.

Verified the job against the actual upstream template (ci-templates/rust-linux-x64.yml), and your reasoning holds point-for-point:

  • Extending .libretro-rust-linux-x64 (the base) rather than -default is exactly right: the base sets no TARGET_ARCH; only -default pins it to x86_64-unknown-linux-gnu. The template builds cargo build --release --target ${TARGET_ARCH}, so setting TARGET_ARCH: aarch64-unknown-linux-gnu here is what redirects it.
  • STRIP is used by the template as $STRIP --strip-unneeded ${CORENAME}_libretro.so, default host strip — so overriding it to aarch64-linux-gnu-strip is necessary and correct.
  • The base defines no before_script, so yours is purely additive (no extends-array clobber), and rustup target add ${RUST_TARGET} matches what .rust-target-install provides — the #91899 pin trap is handled identically. mv target/${TARGET_ARCH}/release/librustynes.so → rustynes_libretro.so resolves correctly with CORENAME=rustynes.

Closed the gap you flagged ("built natively, couldn't test the cross-compile from x86_64"): cargo check --release -p rustynes-libretro --target aarch64-unknown-linux-gnu cross from x86_64 is clean across the whole graph (cpu/ppu/apu/mappers/core/rust-libretro, bindgen included). The buildbot remains the authority on the link/package half.

Companion PR #334 (now merged) adds aarch64-unknown-linux-gnu to this repo's libretro-cross early-warning gate, so the cross-compile your job ships is regression-tested on every GitHub PR from now on — not buildbot-only.

One correction to my earlier note, plus a finding worth passing back to you — both from getting #334 green. I'd first assumed the gate needed no header provisioning (a local cargo check passed clean). On the actual Ubuntu runner it failed at bindgen: /usr/include/stdint.h: 'bits/libc-header-start.h' file not found — the Debian-multiarch split, where glibc's stdint.h pulls in an arch-specific bits/ header a stock x86_64 runner ships only for x86_64. (My local check passed only because an Arch host's glibc headers aren't arch-split.) The fix in #334: install gcc-aarch64-linux-gnu for its libc6-dev-arm64-cross headers under /usr/aarch64-linux-gnu/include, and point bindgen there via --sysroot + -isystem. Confirmed green on the runner afterward.

The relevant bit for your job: that apt-get install gcc-aarch64-linux-gnu you already added does double duty — it's not only the link-time cross linker/STRIP provider, it's also what lands the aarch64 glibc headers bindgen needs to parse libretro.h. So the ingredients are present on the buildbot. The one open question I couldn't settle from here is whether libretro's build image finds those headers automatically the way the buildbot expects, or needs the same explicit hint the GitHub runner did. If the buildbot's aarch64 job ever errors at bindgen with that bits/libc-header-start.h message, the one-line fix is to add to the job:

    BINDGEN_EXTRA_CLANG_ARGS_aarch64_unknown_linux_gnu: "-isystem /usr/aarch64-linux-gnu/include"

(additive — harmless if the image already resolves them). Flagging it so it's a known quantity rather than a surprise; this repo treats the buildbot as observe-post-merge-and-fix-fast, so we'll watch that leg.

On the aside: the rust-libretro 0.3.2 retro_set_environment-before-retro_init panic is upstream and matches what we already track about that (unmaintained) release; RetroArch's call order sidesteps it. Good to have it written down.

Merging now — thanks again for a model contribution.

doublegate added a commit that referenced this pull request Jul 30, 2026
PR #331 adds a `libretro-build-linux-aarch64` job to the GitLab buildbot
recipe, but a GitHub PR cannot run GitLab pipelines, so that cross-compile
had zero pre-merge CI coverage -- the exact blind spot the `libretro-cross`
gate exists to close (added after buildbot pipeline #91899 failed 9/10
jobs on issues reproducible in seconds on a GH runner).

Add `aarch64-unknown-linux-gnu` to the gate's matrix with a per-target
provisioning step. bindgen parses `libretro.h` under `--target aarch64`,
glibc's `stdint.h` pulls in the arch-split `bits/libc-header-start.h`, and
a stock x86_64 runner ships that header only for x86_64 -- so clang fails
with `bits/libc-header-start.h not found` (the same Debian-multiarch miss
the matrix note already documents for the excluded Apple targets).
Installing `gcc-aarch64-linux-gnu` lands `libc6-dev-arm64-cross` under
`/usr/aarch64-linux-gnu/include`; `--sysroot` plus an explicit `-isystem`
send bindgen there. Still `cargo check` only -- the cross linker that
package also provides is unused (linking is the buildbot's job).

The header requirement was surfaced by this gate itself: the aarch64 leg
failed on the runner with exactly that error, even though a local
`cargo check --target aarch64-unknown-linux-gnu` had passed -- because an
Arch dev host's glibc headers are not arch-split while the Debian-multiarch
runner's are. That divergence is precisely the blind spot the gate exists
to catch, now caught pre-merge instead of on the buildbot.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
doublegate added a commit that referenced this pull request Jul 30, 2026
#334)

PR #331 adds a `libretro-build-linux-aarch64` job to the GitLab buildbot
recipe, but a GitHub PR cannot run GitLab pipelines, so that cross-compile
had zero pre-merge CI coverage -- the exact blind spot the `libretro-cross`
gate exists to close (added after buildbot pipeline #91899 failed 9/10
jobs on issues reproducible in seconds on a GH runner).

Add `aarch64-unknown-linux-gnu` to the gate's matrix with a per-target
provisioning step. bindgen parses `libretro.h` under `--target aarch64`,
glibc's `stdint.h` pulls in the arch-split `bits/libc-header-start.h`, and
a stock x86_64 runner ships that header only for x86_64 -- so clang fails
with `bits/libc-header-start.h not found` (the same Debian-multiarch miss
the matrix note already documents for the excluded Apple targets).
Installing `gcc-aarch64-linux-gnu` lands `libc6-dev-arm64-cross` under
`/usr/aarch64-linux-gnu/include`; `--sysroot` plus an explicit `-isystem`
send bindgen there. Still `cargo check` only -- the cross linker that
package also provides is unused (linking is the buildbot's job).

The header requirement was surfaced by this gate itself: the aarch64 leg
failed on the runner with exactly that error, even though a local
`cargo check --target aarch64-unknown-linux-gnu` had passed -- because an
Arch dev host's glibc headers are not arch-split while the Debian-multiarch
runner's are. That divergence is precisely the blind spot the gate exists
to catch, now caught pre-merge instead of on the buildbot.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>

@doublegate doublegate left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed -- DG 7/30

@doublegate doublegate added enhancement New feature or request good first issue Good for newcomers labels Jul 30, 2026
@doublegate
doublegate merged commit 71d48ef into doublegate:main Jul 30, 2026
17 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request good first issue Good for newcomers

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants